package com.linkgie.galaxyframework.utils.io;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import com.linkgie.galaxyframework.utils.BinaryUtils;
import com.linkgie.galaxyframework.utils.CollectionUtils;
import com.linkgie.galaxyframework.utils.IOStatusException;

class ZipFileSystemImpl implements ZipFileSystem {

	private File file;

	private FileSystem fs;

	public ZipFileSystemImpl(String path) {
		this.file = new File(path);
	}

	public ZipFileSystemImpl(File file) {
		this.file = file;
	}

	protected String getUriScheme() {
		return "";
	}

	@Override
	public boolean exist(String path) {
		Path entryPath = getEntryPath(path);
		return Files.exists(entryPath);
	}

	@Override
	public byte[] readBytes(String path) {
		Path entryPath = getEntryPath(path);
		try {
			return Files.readAllBytes(entryPath);
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	@Override
	public InputStream read(String path) {
		Path entryPath = getEntryPath(path);
		try {
			if (!Files.exists(entryPath)) {
				return new ByteArrayInputStream(BinaryUtils.EMPTY_BYTES);
			}
			InputStream in = Files.newInputStream(entryPath, StandardOpenOption.READ);
			return in;
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	protected void createFileSystem() {
		if (this.fs != null) {
			throw new IllegalStateException("This file is opened! Can't create or open again!");
		}
		Map<String, String> env = new HashMap<>();
		env.put("create", "true");
		URI uri = URI.create(getUriScheme() + file.toPath().toUri());

		try {
			this.fs = FileSystems.newFileSystem(uri, env);

			postInitialized();
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	protected void openFileSystem() {
		if (this.fs != null) {
			throw new IllegalStateException("This file is opened! Can't create or open again!");
		}
		try {
			this.fs = FileSystems.newFileSystem(file.toPath(), null);

			postInitialized();
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	protected void postInitialized() {
	}

	/**
	 * 列出指定路径下的直接子项；
	 * <p>
	 * 此操作不进行递归检索；
	 * 
	 * @param path
	 * @return
	 */
	@Override
	public String[] list(String path) {
		try {
			Path entryPath = getEntryPath(path);
			Iterator<Path> subEntries = Files.list(entryPath).iterator();
			return CollectionUtils.toArray(subEntries, String.class, p -> p.toString());
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	@Override
	public void create(String path, InputStream in) {
		Path entryPath = getEntryPath(path);
		try {
			// 父目录不存在时，先创建父目录；
			Path parentDir = entryPath.getParent();
			if (parentDir != null && !Files.exists(parentDir)) {
				Files.createDirectories(parentDir);
			}

			// 创建文件；
			try (OutputStream out = Files.newOutputStream(entryPath, StandardOpenOption.CREATE)) {
				BinaryUtils.copy(in, out);
			}
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	@Override
	public void create(String path, byte[] content) {
		Path entryPath = getEntryPath(path);
		try {
			// 父目录不存在时，先创建父目录；
			Path parentDir = entryPath.getParent();
			if (parentDir != null && !Files.exists(parentDir)) {
				Files.createDirectories(parentDir);
			}
			// 创建文件；
			Files.write(entryPath, content, StandardOpenOption.CREATE);
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	@Override
	public void write(String path, byte[] content) {
		Path entryPath = getEntryPath(path);
		try {
			// 父目录不存在时，先创建父目录；
			Path parentDir = entryPath.getParent();
			if (parentDir != null && !Files.exists(parentDir)) {
				Files.createDirectories(parentDir);
			}
			// 写入文件；
			Files.write(entryPath, content, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	@Override
	public void write(String path, InputStream in) {
		try (OutputStream out = openOrCreateEntry(path)) {
			BinaryUtils.copy(in, out);
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	/**
	 * 以覆盖方式打开或新建文件；
	 * 
	 * @param path
	 * @return
	 */
	protected OutputStream openOrCreateEntry(String path) {
		Path entryPath = getEntryPath(path);
		try {
			// 父目录不存在时，先创建父目录；
			Path parentDir = entryPath.getParent();
			if (parentDir != null && !Files.exists(parentDir)) {
				Files.createDirectories(parentDir);
			}
			// 创建或覆盖写入；
			OutputStream out = Files.newOutputStream(entryPath, StandardOpenOption.TRUNCATE_EXISTING,
					StandardOpenOption.CREATE);
			return out;
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	@Override
	public void append(String path, byte[] content) {
		Path entryPath = getEntryPath(path);
		try {
			Files.write(entryPath, content, StandardOpenOption.APPEND);
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	@Override
	public void append(String path, InputStream in) {
		Path entryPath = getEntryPath(path);
		try (OutputStream out = Files.newOutputStream(entryPath, StandardOpenOption.APPEND)) {
			BinaryUtils.copy(in, out);
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	@Override
	public void delete(String path) {
		Path entryPath = getEntryPath(path);
		try {
			Files.deleteIfExists(entryPath);
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	@Override
	public void deleteAll(String path) {
		Path entryPath = getEntryPath(path);
		FileUtils.deleteAll(entryPath, false);
	}

	private Path getEntryPath(String path) {
		Path entryPath = this.fs.getPath(path);
		return entryPath;
	}

	@Override
	public final synchronized void close() {
		FileSystem zipFs = this.fs;
		if (zipFs != null) {

			try {
				beforeClose();

				this.fs = null;
				zipFs.close();

				afterClose();
			} catch (IOException e) {
				throw new IOStatusException(e.getMessage(), e);
			}
		}
	}

	protected void beforeClose() throws IOException {
	}

	protected void afterClose() throws IOException {
	}
}